﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;

namespace Util.Media.Flash
{
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public abstract class FillStyle
    {
        public FillStyleType Type { get; set; }

        protected abstract void Deserialize(FlashReader reader, int shapeVersion);

        public abstract void Serialize(FlashWriter writer, int shapeVersion);

        public override string ToString()
        {
            return Type.ToString();
        }

        /// <summary>Read FILLSTYLEARRAY.</summary>
        public static FillStyle[] ReadObjectArray(FlashReader reader, int shapeVersion)
        {
            ushort count = reader.ReadByte();
            if (count == 0xFF && (shapeVersion == 2 || shapeVersion == 3))
                count = reader.ReadUInt16();

            FillStyle[] array = new FillStyle[count];
            for (int i = 0; i < count; i++)
                array[i] = ReadObject(reader, shapeVersion);

            return array;
        }

        /// <summary>Write FILLSTYLEARRAY.</summary>
        public static void SerializeArray(FlashWriter writer, FillStyle[] array, int shapeVersion)
        {
            if (!(shapeVersion >= 1 && shapeVersion <= 3))
                throw new ArgumentException("Unsupported shapeVersion.");

            if (array == null)
            {
                writer.Write((byte)0);
                return;
            }

            // Write FillStyleCount.
            if (shapeVersion == 1)
            {
                if (array.Length > 0xFF)
                    throw new ArgumentException("Too many elements.");
                writer.Write((byte)array.Length);
            }
            else
            {
                if (array.Length > 0xFFFF)
                    throw new ArgumentException("Too many elements.");

                if (array.Length < 0xFF)
                {
                    writer.Write((byte)array.Length);
                }
                else
                {
                    writer.Write((byte)0xFF);
                    writer.Write((ushort)array.Length);
                }
            }

            // Write array.
            for (int i = 0; i < array.Length; i++)
            {
                writer.Write((byte)array[i].Type);
                array[i].Serialize(writer, shapeVersion);
            }
        }

        /// <summary>Read FILLSTYLE.</summary>
        public static FillStyle ReadObject(FlashReader reader, int shapeVersion)
        {
            FillStyle fill;
            FillStyleType type = (FillStyleType)reader.ReadByte();
            switch (type)
            {
                case FillStyleType.Solid:
                    fill = new SolidFillStyle();
                    break;
                case FillStyleType.LineGradient:
                case FillStyleType.RadialGradient:
                case FillStyleType.FocalRadialGradient:
                    fill = new GradientFillStyle();
                    break;
                case FillStyleType.RepeatingBitmap:
                case FillStyleType.ClippedBitmap:
                case FillStyleType.NonSmoothedRepeatingBitmap:
                case FillStyleType.NonSmoothedClippedBitmap:
                    fill = new BitmapFillStyle();
                    break;
                default:
                    throw new InvalidDataException("Unknown FillStyleType.");
            }
            fill.Type = type;
            fill.Deserialize(reader, shapeVersion);
            return fill;
        }
    }

    public class SolidFillStyle : FillStyle
    {
        public Color Color { get; set; }

        protected override void Deserialize(FlashReader reader, int shapeVersion)
        {
            throw new NotImplementedException();
        }

        public override void Serialize(FlashWriter writer, int shapeVersion)
        {
            throw new NotImplementedException();
        }
    }

    public class GradientFillStyle : FillStyle
    {
        protected override void Deserialize(FlashReader reader, int shapeVersion)
        {
            throw new NotImplementedException();
        }

        public override void Serialize(FlashWriter writer, int shapeVersion)
        {
            throw new NotImplementedException();
        }
    }

    public class BitmapFillStyle : FillStyle
    {
        public ushort BitmapId { get; set; }

        [TypeConverter(typeof(ExpandableObjectConverter))]
        public Matrix BitmapMatrix { get; set; }

        protected override void Deserialize(FlashReader reader, int shapeVersion)
        {
            this.BitmapId = reader.ReadUInt16();
            this.BitmapMatrix = reader.ReadMatrix();
        }

        public override void Serialize(FlashWriter writer, int shapeVersion)
        {
            writer.Write(this.BitmapId);
            writer.WriteMatrix(this.BitmapMatrix);
        }
    }

    public enum FillStyleType : byte
    {
        Solid = 0x00,
        LineGradient = 0x10,
        RadialGradient = 0x12,
        FocalRadialGradient = 0x13,
        RepeatingBitmap = 0x40,
        ClippedBitmap = 0x41,
        NonSmoothedRepeatingBitmap = 0x42,
        NonSmoothedClippedBitmap = 0x43,
    }

    public class LineStyle
    {
        public float Width { get; set; }
        public Color Color { get; set; }

        public static LineStyle[] ReadObjectArray(FlashReader reader, int shapeVersion)
        {
            ushort count = reader.ReadByte();
            if (count == 0xFF)
                count = reader.ReadUInt16();

            LineStyle[] array = new LineStyle[count];
            for (int i = 0; i < count; i++)
                array[i] = ReadObject(reader, shapeVersion);

            return array;
        }

        public static LineStyle ReadObject(FlashReader reader, int shapeVersion)
        {
            if (!(shapeVersion >= 1 && shapeVersion <= 3))
                throw new ArgumentException("Incorrect shapeVersion.");

            LineStyle line = new LineStyle();

            line.Width = reader.ReadUInt16() / 20.0f;

            if (shapeVersion == 1 || shapeVersion == 2)
                line.Color = reader.ReadRGB();
            else if (shapeVersion == 3)
                line.Color = reader.ReadRGBA();

            return line;
        }

        public void Serialize(FlashWriter writer, int shapeVersion)
        {
            writer.Write((ushort)Math.Round(this.Width * 20.0f));
            if (shapeVersion == 1 || shapeVersion == 2)
                writer.WriteRGB(this.Color);
            else if (shapeVersion == 3)
                writer.WriteRGBA(this.Color);
        }

        /// <summary>Write LINESTYLEARRAY.</summary>
        public static void SerializeArray(FlashWriter writer, LineStyle[] array, int shapeVersion)
        {
            if (!(shapeVersion >= 1 && shapeVersion <= 3))
                throw new ArgumentException("Unsupported shapeVersion.");

            // Write LineStyleCount.
            if (array == null)
            {
                writer.Write((byte)0);
                return;
            }
            if (array.Length > 0xFFFF)
                throw new ArgumentException("Too many elements.");
            if (array.Length < 0xFF)
            {
                writer.Write((byte)array.Length);
            }
            else
            {
                writer.Write((byte)0xFF);
                writer.Write((ushort)array.Length);
            }

            // Write array.
            for (int i = 0; i < array.Length; i++)
            {
                array[i].Serialize(writer, shapeVersion);
            }
        }
    }


    [TypeConverter(typeof(ExpandableObjectConverter))]
    public abstract class ShapeRecord
    {
        public static ShapeRecord[] DeserializeArray(FlashReader reader)
        {
            List<ShapeRecord> records = new List<ShapeRecord>();

            reader.EnterBitValueMode();
            uint numFillBits = reader.ReadUBits(4);
            uint numLineBits = reader.ReadUBits(4);

            while (true)
            {
                // According to the SWF spec, each record should be aligned
                // on a byte boundary. But who know what's in practice...
                //reader.EnterBitValueMode();
                uint typeFlag = reader.ReadUBits(1);
                if (typeFlag == 0) // EndShapeRecord and StyleChangeRecord
                {
                    StateFlags state = (StateFlags)reader.ReadUBits(5);
                    if (state == StateFlags.None) // EndShapeRecord
                    {
                        //reader.LeaveBitValueMode();
                        records.Add(new EndShapeRecord());
                        break;
                    }
                    else // StyleChangeRecord
                    {
                        StyleChangeRecord r = new StyleChangeRecord();
                        r.Flags = state;
                        if (state.HasFlag(StateFlags.MoveTo))
                        {
                            uint nbits = reader.ReadUBits(5);
                            r.MoveDeltaX = reader.ReadSBits(nbits) / 20.0f;
                            r.MoveDeltaY = reader.ReadSBits(nbits) / 20.0f;
                        }
                        if (state.HasFlag(StateFlags.FillStyle0))
                        {
                            r.FillStyleIndex0 = reader.ReadUBits(numFillBits);
                        }
                        if (state.HasFlag(StateFlags.FillStyle1))
                        {
                            r.FillStyleIndex1 = reader.ReadUBits(numFillBits);
                        }
                        if (state.HasFlag(StateFlags.LineStyle))
                        {
                            r.LineStyleIndex = reader.ReadUBits(numLineBits);
                        }
                        if (state.HasFlag(StateFlags.NewStyles))
                        {
                            // do we need byte-alignment below?????
                            throw new NotImplementedException();
#if false
                            r.NewFillStyles = FillStyle.ReadObjectArray(reader, 3);
                            r.NewLineStyles = LineStyle.ReadObjectArray(reader, 3);
                            reader.EnterBitValueMode();
                            numFillBits = reader.ReadUBits(4);
                            numLineBits = reader.ReadUBits(4);
#endif
                        }
                        records.Add(r);
                    }
                }
                else // StraightEdgeRecord or CurveEdgeRecord
                {
                    bool isStraightEdge = (reader.ReadUBits(1) != 0);
                    if (isStraightEdge)
                    {
                        StraightEdgeRecord r = new StraightEdgeRecord();
                        r.Deserialize(reader);
                        records.Add(r);
                    }
                    else
                    {
                        throw new NotImplementedException();
                    }
                }
                //reader.LeaveBitValueMode();
            }
            reader.LeaveBitValueMode();
            return records.ToArray();
        }

        public static void SerializeArray(
            FlashWriter writer, ShapeRecord[] records, int shapeVersion)
        {
            uint numFillBits = 15; // just to be safe reader.ReadUBits(4);
            uint numLineBits = 15;

            writer.EnterBitValueMode();
            writer.WriteUBits(numFillBits, 4);
            writer.WriteUBits(numLineBits, 4);

            foreach (ShapeRecord record in records)
            {
                // According to the SWF spec, each record should be aligned
                // on a byte boundary. But who know what's in practice...
                //writer.EnterBitValueMode();
                uint typeFlag = (record is EndShapeRecord ||
                                 record is StyleChangeRecord) ? 0u : 1u;
                writer.WriteUBits(typeFlag, 1);

                if (record is EndShapeRecord)
                {
                    writer.WriteUBits(0, 5);
                }
                else if (record is StyleChangeRecord)
                {
                    StyleChangeRecord r = record as StyleChangeRecord;
                    StateFlags state = r.Flags;
                    writer.WriteUBits((uint)state, 5);

                    if (state.HasFlag(StateFlags.MoveTo))
                    {
                        uint nbits = 31;
                        writer.WriteUBits(nbits, 5);
                        writer.WriteSBits((int)(r.MoveDeltaX * 20.0f), nbits);
                        writer.WriteSBits((int)(r.MoveDeltaY * 20.0f), nbits);
                    }
                    if (state.HasFlag(StateFlags.FillStyle0))
                    {
                        writer.WriteUBits(r.FillStyleIndex0, numFillBits);
                    }
                    if (state.HasFlag(StateFlags.FillStyle1))
                    {
                        writer.WriteUBits(r.FillStyleIndex1, numFillBits);
                    }
                    if (state.HasFlag(StateFlags.LineStyle))
                    {
                        writer.WriteUBits(r.LineStyleIndex, numLineBits);
                    }
                    if (state.HasFlag(StateFlags.NewStyles))
                    {
                        // do we need byte-alignment below?????
                        throw new NotImplementedException();
#if false
                            r.NewFillStyles = FillStyle.ReadObjectArray(reader, 3);
                            r.NewLineStyles = LineStyle.ReadObjectArray(reader, 3);
                            reader.EnterBitValueMode();
                            numFillBits = reader.ReadUBits(4);
                            numLineBits = reader.ReadUBits(4);
#endif
                    }
                }
                else if (record is StraightEdgeRecord)
                {
                    writer.WriteUBits(1, 1); // StraightFlag
                    (record as StraightEdgeRecord).Serialize(writer);
                }
                else
                {
                    throw new NotImplementedException();
                }
                //reader.LeaveBitValueMode();
            }
            writer.LeaveBitValueMode();
        }
    }

    public class EndShapeRecord : ShapeRecord
    {
    }

    public class StyleChangeRecord : ShapeRecord
    {
        public StateFlags Flags { get; set; }

        [Description("Number of pixels to move horizontally.")]
        public float MoveDeltaX { get; set; }

        [Description("Number of pixels to move vertically.")]
        public float MoveDeltaY { get; set; }

        public uint FillStyleIndex0 { get; set; }
        public uint FillStyleIndex1 { get; set; }

        public uint LineStyleIndex { get; set; }

        public FillStyle[] NewFillStyles { get; set; }
        public LineStyle[] NewLineStyles { get; set; }
    }

    [Flags]
    public enum StateFlags
    {
        None = 0,
        MoveTo = 0x01,
        FillStyle0 = 0x02,
        FillStyle1 = 0x04,
        LineStyle = 0x08,
        NewStyles = 0x10,
    }

    public class StraightEdgeRecord : ShapeRecord
    {
        public float DeltaX { get; set; }
        public float DeltaY { get; set; }

        public void Deserialize(FlashReader reader)
        {
            // Start reading from NumBits.
            uint numBitsMinus2 = reader.ReadUBits(4);
            bool isGeneralLine = (reader.ReadUBits(1) != 0);
            if (!isGeneralLine)
            {
                bool isVerticalLine = (reader.ReadUBits(1) != 0);
                if (isVerticalLine)
                {
                    this.DeltaX = 0;
                    this.DeltaY = reader.ReadSBits(numBitsMinus2 + 2) / 20.0f;
                }
                else
                {
                    this.DeltaX = reader.ReadSBits(numBitsMinus2 + 2) / 20.0f;
                    this.DeltaY = 0;
                }
            }
            else
            {
                this.DeltaX = reader.ReadSBits(numBitsMinus2 + 2) / 20.0f;
                this.DeltaY = reader.ReadSBits(numBitsMinus2 + 2) / 20.0f;
            }
        }

        /// <summary>
        /// Write STRAIGHTEDGERECORD without the TypeFlag and StraightFlag field.
        /// </summary>
        /// <param name="writer"></param>
        public void Serialize(FlashWriter writer)
        {
            // Start reading from NumBits.
            uint numBitsMinus2 = 15; // actual numBits = 17
            writer.WriteUBits(numBitsMinus2, 4);

            bool isGeneralLine = true;
            writer.WriteUBits(isGeneralLine ? 1u : 0u, 1);

#if false
            if (!isGeneralLine)
            {
                bool isVerticalLine = (reader.ReadUBits(1) != 0);
                if (isVerticalLine)
                {
                    this.DeltaX = 0;
                    this.DeltaY = reader.ReadSBits(numBitsMinus2 + 2) / 20.0f;
                }
                else
                {
                    this.DeltaX = reader.ReadSBits(numBitsMinus2 + 2) / 20.0f;
                    this.DeltaY = 0;
                }
            }
            else
#endif
            {
                writer.WriteSBits((int)Math.Round(this.DeltaX * 20.0f), numBitsMinus2 + 2);
                writer.WriteSBits((int)Math.Round(this.DeltaY * 20.0f), numBitsMinus2 + 2);
            }
        }
    }
}
